package cn.xiaobai.service;

import cn.hutool.core.util.ArrayUtil;
import cn.xiaobai.config.HolidayProperties;
import cn.xiaobai.config.PlanProperties;
import cn.xiaobai.enums.TimeTypeEnum;
import cn.xiaobai.utils.TimeUtils;
import cn.xioabai.common.utility.NumberUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Component;

import java.awt.*;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 自动根据当前日志创建文件
 *
 * @Author yangdaji
 * @Date 2023/4/6
 * @PackageName cn.xiaobai
 * @ClassName AutoCreateFile
 */
@Slf4j
@Component
public class AutoCreateFileService implements InitializingBean, SchedulingConfigurer {

    /**
     * 自定义工作目录
     */
    private String workPath;

    /**
     * 文件匹配深度, 默认4层
     */
    private Integer maxdepth;

    /**
     * 节假日json 文件名称
     */
    private String holidayPath;

    /**
     * 是否大小周
     */
    private Boolean weekSize;

    /**
     * 日志欢迎词
     */
    private String welcomeName;

    /**
     * 默认工作目录
     */
    private String defaultWorkPath;

    private String hour;

    private String min;

    /**
     * 是否马上执行
     */
    private Boolean always;

    /**
     * 是否马上执行
     */
    private Boolean oneTime;

    /**
     * 指定时分, 默认在周一执行
     */
    private String cronTemplate;

    /**
     * 节假日默认配置文件名称
     */
    private final String HOLIDAY_DEFAULT_NAME = "holiday.json";


    /**
     * @param holidayProperties 工作日志配置
     * @param planProperties    计划配置
     */
    public AutoCreateFileService(HolidayProperties holidayProperties, PlanProperties planProperties) {
        this.workPath = holidayProperties.getPath();
        this.maxdepth = holidayProperties.getMaxdepth();
        this.holidayPath = holidayProperties.getHolidayPath();
        this.weekSize = holidayProperties.getWeekSize();
        this.welcomeName = holidayProperties.getWelcomeName();
        this.defaultWorkPath = holidayProperties.getDEFAULT_WORK_PATH();

        this.always = planProperties.getAlways();
        this.oneTime = planProperties.getOneTime();
        this.cronTemplate = planProperties.getCronTemplate();
        this.min = planProperties.getMin();
        this.hour = planProperties.getHour();

    }

    @Autowired
    private HolidayService holidayService;

    @Override
    public void afterPropertiesSet() throws Exception {
        // 立即执行, 则启动自动运行一次, 后续则按照定时周期执行
        if (always) {
            start(workPath);
        }
    }

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        if (oneTime) {
            log.info("执行结束!");
            return;
        }
        taskRegistrar.addTriggerTask(() -> {
            start(workPath);
        }, triggerContext -> {
            String cron = String.format(cronTemplate, min, hour);
            CronTrigger cronTrigger = new CronTrigger(cron);
            return cronTrigger.nextExecutionTime(triggerContext);
        });
    }

    /**
     * 程序启动执行
     *
     * @param workPath 工作目录
     */
    private void start(String workPath) {
        if (StringUtils.isNoneBlank(workPath)) {
            File file = new File(workPath);
            if (!file.exists()) {
                file.mkdirs();
            }
            // 非目录不操作
            if (file.isDirectory()) {
                log.info("使用自定义工作目录: {}", workPath);
            } else {
                log.warn("自定义的目录为非目录,终止执行,请检查配置: {}", workPath);
                return;
            }
        } else {
            // 默认目录
            File file = new File(defaultWorkPath);
            if (!file.exists()) {
                file.mkdirs();
            }
            workPath = defaultWorkPath;
            log.info("使用默认工作目录: {}", defaultWorkPath);
        }

        String targetPath = findPathCreateFile(workPath);
        // 这里进行打开文件
        if (Objects.isNull(targetPath)) {
            return;
        }
        execCmd(targetPath);

    }

    // function method

    /**
     * 查找创建工作日文件
     *
     * @param targetPath 目标目录
     * @return
     */
    private String findPathCreateFile(String targetPath) {
        // 获取年月日
        Date date = new Date();
        int year = TimeUtils.dateToYearMonthDay(date, TimeTypeEnum.YEAR.getType());
        int month = TimeUtils.dateToYearMonthDay(date, TimeTypeEnum.MONTH.getType()) + 1;
        int day = TimeUtils.dateToYearMonthDay(date, TimeTypeEnum.DAY.getType());
        String yearMonthDay = year + "-" + month + "-" + day;
        // 判断是否是节假日, 节假日不进行操作
        Boolean isHoliday = holidayService.getHolidayToMapByDate(yearMonthDay,
                StringUtils.isBlank(holidayPath) ?
                        targetPath + HOLIDAY_DEFAULT_NAME
                        : holidayPath + HOLIDAY_DEFAULT_NAME);
        if (isHoliday == null) {
            log.error("未匹配到当前日期的节假日状态, 请检查节假日缓存文件是否正常");
            return null;
        }
        if (isHoliday) {
            // 节假日,跳过
            log.info("判断当前日期为-> [节假日],跳过创建");
            return null;
        }

        log.info("当前日期不是节假日, 创建工作日志文件");
        // 计算一周后的日期, 周五, 周六
        Date friDaydateCalcul = TimeUtils.fridayWeek(date);
        Date staturDaydateCalcul = TimeUtils.fridayWeekSaturday(date);

        // 如果是大小周, 计算周六奇数偶数, 默认双休,算周五
        Date dateCalcul = friDaydateCalcul;
        if (weekSize) {
            int weekSizeDay = TimeUtils.dateToYearMonthDay(staturDaydateCalcul, TimeTypeEnum.DAY.getType());
            // 偶数(大小周双休), 奇数(大小周单休)
            if (weekSizeDay % 2 != 0) {
                dateCalcul = staturDaydateCalcul;
            }
        }

        // 获取一周后的月日
        int weekMonth = TimeUtils.dateToYearMonthDay(dateCalcul, TimeTypeEnum.MONTH.getType()) + 1;
        int weekDay = TimeUtils.dateToYearMonthDay(dateCalcul, TimeTypeEnum.DAY.getType());
        String weekDateFile = weekMonth + "." + weekDay + ".txt";

        // 拼接文件命名
        String currentDateFile = month + "." + day + "-" + weekDateFile;

        String yearPath = targetPath + year;
        String monthPath = yearPath + "\\" + month;

        // 搜索指定目录
        // 1. 匹配年份文件夹
        File file = Paths.get(targetPath).toFile();
        Map<Integer, Map<Integer, File>> yearNameToFileMap = new HashMap<>();
        // 达到匹配深度则不在匹配
        while (--maxdepth != -1) {
            File[] files = file.listFiles();
            for (File yearFile : files) {
                // 只匹配目录
                if (yearFile.isDirectory()) {
                    // 获取年份文件夹
                    String yearName = yearFile.getName();
                    if (NumberUtils.isNumber(yearName)) {
                        File[] listFiles = yearFile.listFiles();
                        if (listFiles != null) {
                            Map<Integer, File> monthNameToFileMap = new HashMap<>();
                            for (File monthFile : listFiles) {
                                if (monthFile.isDirectory()) {
                                    // 获取月份文件夹
                                    String monthName = monthFile.getName();
                                    if (NumberUtils.isNumber(monthName)) {
                                        monthNameToFileMap.put(Integer.valueOf(monthName), monthFile);
                                    }
                                }
                            }
                            yearNameToFileMap.put(Integer.valueOf(yearName), monthNameToFileMap);
                        }
                    }
                }
            }

            // 判断年份文件夹
            if (yearNameToFileMap.containsKey(year)) {
                Map<Integer, File> monthFileMap = yearNameToFileMap.get(year);
                // 判断月份文件夹
                if (monthFileMap.containsKey(month)) {
                    File listFile = monthFileMap.get(month);
                    File[] listFiles = listFile.listFiles();
                    if (ArrayUtil.isNotEmpty(listFiles)) {
                        for (File dayFile : listFiles) {
                            // 进行匹配对应的文件
                            String dayFileName = dayFile.getName();
                            if (dayFileName.contains(weekDateFile)) {
                                String dayFilePath = dayFile.getPath();
                                log.info("根据日期 [{}] 匹配到对应的文件: {}, 所在文件目录: {}", yearMonthDay, dayFileName, dayFilePath);
                                return dayFilePath;
                            }
                        }
                    }

                    // 如果日期下一次没有文件或者完后仍然未匹配到当前对应的文件, 则进行创建
                    String absolutePath = listFile.getAbsolutePath() + "\\" + currentDateFile;
                    log.info("创建目标名称不存在的文件: {}", absolutePath);
                    createFile(absolutePath, "file");
                } else {
                    // 如果当前月份文件夹不存在则进行创建
                    log.info("创建不存在的月份目录: {}", monthPath);
                    createFile(monthPath, "directory");
                }
            } else {
                // 如果当前年份文件夹不存在则进行创建
                log.info("创建不存在的年份目录: {}", yearPath);
                createFile(yearPath, "directory");
            }
        }
        return null;
    }

    /**
     * 根据类型新建文件或文件夹
     *
     * @param absolutePath    路径
     * @param fileOrDirectory 文件或路径
     */
    private void createFile(String absolutePath, String fileOrDirectory) {

        if ("file".equals(fileOrDirectory)) {
            try (FileWriter fw = new FileWriter(absolutePath);
                 BufferedWriter bw = new BufferedWriter(fw)) {
                String str = new String(welcomeName.getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8);
                bw.write(str);
                bw.flush();
                log.info("{} 文件创建完成", absolutePath);
            } catch (IOException e) {
                log.error("创建文件失败: {}", e.getMessage());
            }
        } else if ("directory".equals(fileOrDirectory)) {
            File file = new File(absolutePath);
            if (!file.exists()) {
                file.mkdirs();
                log.info("{} 目录创建完成", absolutePath);
            }
        }

    }

    /**
     * 执行 cmd 命令
     *
     * @param fileName 文件名称
     */
    private void execCmd(String fileName) {
        Runtime runtime = Runtime.getRuntime();
        try {
            // 直接使用本机桌面上注册的应用程序打开
            Desktop.getDesktop().open(Paths.get(fileName).toFile());
        } catch (Exception e) {
            log.error("使用本机注册程序打开失败: {}", e.getMessage());
            try {
                // 打开失败则使用自带的记事本打开
                String[] args = {"cmd", "/c", "notepad", fileName};
                runtime.exec(args);
            } catch (IOException ex) {
                log.error("执行cmd 命令%s失败: {}", e.getMessage());
            }

        }
    }
}
